Antialiased font rendering

Joric^Proxium

In a 64k intro by Foobug (Who Needs Raytrace Anyway - 3rd place of Assembly 2000), you, maybe, noticed some truetype font messages while initalising. These messages also appear later, when the intro is running. Why write something twice, and so fast that nobody can read it? Here is the answer - in the raytrace.nfo:

--- raytrace.nfo ---

Q: How The Hell Is Someone Supposed To Render Antialiased Windows Fonts
   Into A Background Buffer?

A: i don't know.


 creating a dc with CreateCompatibleDC() works fine, but the text just
 isn't antialiased. don't know why. so i did it this way:


   void RenderFont(void *buf, int w, int h, char *str, int strlen, int size,
                                                               char *fontname)
   {
      HDC hdc=GetDC(our_window_handle);
      HDC hdcCompat=CreateCompatibleDC(hdc);
   
      HBITMAP hb;
      HGDIOBJ hbold;
   
      BITMAPINFO bmi;
      int *bmdata;
   
      bmi.bmiHeader.biSize=sizeof(bmi.bmiHeader);
      bmi.bmiHeader.biWidth=w;
      bmi.bmiHeader.biHeight=-h;
      bmi.bmiHeader.biPlanes=1;
      bmi.bmiHeader.biBitCount=24;
      bmi.bmiHeader.biCompression=BI_RGB;
      bmi.bmiHeader.biSizeImage=0;
      bmi.bmiHeader.biClrUsed=0;
      bmi.bmiHeader.biClrImportant=0;
   
      hb=CreateDIBSection(hdcCompat, &bmi, DIB_RGB_COLORS,
                         (void**)&bmdata, NULL, 0);
   
      hbold=SelectObject(hdcCompat, hb);
   
      RECT r;
      r.top=0;
      r.left=0;
      r.right=w;
      r.bottom=h;
   
      FillRect(hdc, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));
   
      HFONT hf=CreateFont(size, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
                          ANSI_CHARSET,
                          OUT_DEFAULT_PRECIS,
                          CLIP_DEFAULT_PRECIS,
                          ANTIALIASED_QUALITY,
                          DEFAULT_PITCH,
                          fontname);
   
      SelectObject(hdc, hf);
      
      SetBkMode(hdc, TRANSPARENT);
      SetTextColor(hdc, 0xFFFFFF);
      TextOut(hdc, 0, 0, str, strlen);
   
      BitBlt(hdcCompat, 0, 0, w, h, hdc, 0, 0, SRCCOPY);
   
   // To clear the hdc afterwards
   //   FillRect(hdc, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));
   
      GdiFlush();
   
      unsigned char *colorbuf=(unsigned char*)bmdata;
      for(int i=0;i<w*h;i++) {
         ((int*)buf)[i]=colorbuf[i*3]+colorbuf[i*3+1]*256+
                        colorbuf[i*3+2]*256*256;
      }
   
      SelectObject(hdcCompat, hbold);
   
      DeleteObject(hf);
      DeleteObject(hb);
      ReleaseDC(winhandle, hdcCompat);
      ReleaseDC(winhandle, hdc);
   }

 
 so it draws the text into the main window and then copies it into my
 own dib and then copies the pixels from the dib into memory.

--- raytrace.nfo ---

This .nfo file, for a pity, rises a lot of questions in win32 programming conferences. Believe me, there is nothing complex. Really, Windows GDI can't render an antialiased font to a DIB section. But this problem can be easily solved - let's just modify this code to work with a device-dependent bitmap, and convert this bitmap afterwards.

...
HDC hdc=GetDC(NULL);
HDC mdc = CreateCompatibleDC(hdc);
HBITMAP bm = CreateCompatibleBitmap(hdc,w,h);     
...
SelectObject(mdc, bm);
SelectObject(mdc, hf);      
SetBkMode(mdc, TRANSPARENT);
SetTextColor(mdc, 0xFFFFFF);
TextOut(mdc, 0, 0, str, strlen(str));

BITMAPINFO bmi;    

bmi.bmiHeader.biSize=sizeof(bmi.bmiHeader);
bmi.bmiHeader.biWidth=256;
bmi.bmiHeader.biHeight=256;
bmi.bmiHeader.biPlanes=1;
bmi.bmiHeader.biBitCount=32;
bmi.bmiHeader.biCompression=BI_RGB;

GetDIBits(mdc,bm,0,256,buf,&bmi,DIB_RGB_COLORS);
...

The GetDIBits function makes the conversion we need. As you can see, the new BITMAPINFO structure is slightly modified - now it fits the format I use for textures (RGBA). You can also add some code to make a proper alpha channel and so on. Here is the sourcecode you can compile. Regards and wishes to the scene, and happy coding!

//Antialiased font renderer by Joric^Proxium
//to compile use:
//cl /W3 /MD /Oax /Oi /Gs test.cpp /link gdi32.lib /opt:nowin98 

#include <Windows.h>
#include <stdio.h>

#pragma pack(1)
struct nlTGAHeader
{
        char text_size;
        char map_type;
        char data_type;
        short map_org;
        short map_length;
        char cmap_bits;
        short x_offset;
        short y_offset;
        short width;
        short height;
        char data_bits;
        char im_type;
};
#pragma pack()

void SaveTexture(char *filename, int *texture)
{
        FILE *fp;
        int i,c;
        unsigned char r,g,b;
        unsigned char *data;

        fp=fopen(filename,"wb");
        data=(unsigned char*)malloc(256*256*3); 
        unsigned char *d=data;

        for (i=0; i < 256*256; i++)
        {
                c=texture[i];
                r=(c>>16)&0xFF;
                g=(c>>8)&0xFF;
                b=(c)&0xFF;
                *d++=b;
                *d++=g;
                *d++=r;
        }

        nlTGAHeader header;

        memset(&header,0,sizeof(header));
        header.data_type=2;
        header.width=256;
        header.height=256;
        header.data_bits=0x18;

        fwrite((void*)&header,sizeof(nlTGAHeader),1,fp);                
        fwrite(data,256*256*3,1,fp);
        fclose(fp);
        free(data);

}

void RenderFont(int x, int y, int size, char *str, char *fontname,
                        void *buf, int w, int h)
   {

        HDC hdc=GetDC(NULL);
        HDC mdc = CreateCompatibleDC(hdc);
        HBITMAP bm = CreateCompatibleBitmap(hdc,w,h);     

        HFONT hf=CreateFont(size, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE,
        ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, 
        ANTIALIASED_QUALITY, DEFAULT_PITCH, fontname);         

        RECT r;
        r.top=0;
        r.left=0;
        r.right=w;
        r.bottom=h;

        SelectObject(mdc, bm);
        FillRect(mdc, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));

        SelectObject(mdc, hf);      
        SetBkMode(mdc, TRANSPARENT);
        SetTextColor(mdc, 0xFFFFFF);
        TextOut(mdc, 0, 0, str, strlen(str));
        
        BITMAPINFO bmi;    
   
        bmi.bmiHeader.biSize=sizeof(bmi.bmiHeader);
        bmi.bmiHeader.biWidth=256;
        bmi.bmiHeader.biHeight=256;
        bmi.bmiHeader.biPlanes=1;
        bmi.bmiHeader.biBitCount=32;
        bmi.bmiHeader.biCompression=BI_RGB;

        GetDIBits(mdc,bm,0,256,buf,&bmi,DIB_RGB_COLORS);

        DeleteObject(hf);
        DeleteObject(bm);    
}

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                                   LPTSTR lpCmdLine, int nCmdShow)
{
        int buf[256*256];

        RenderFont(0,0,70,".the .test","Arial", buf,256,256);

        MessageBox(NULL,"See texture.tga","System message",MB_OK);

        SaveTexture("texture.tga",buf);

        return 0;
}

Joric^Proxium